// Copyright (c) 2005-2008, Simon Howard
// Permission to use, copy, modify, and/or distribute this software
// for any purpose with or without fee is hereby granted, provided
// that the above copyright notice and this permission notice appear
// in all copies.
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
// NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#![allow(dead_code)]

use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::mem;
use std::ptr;

const NUM_TEST_VALUES: usize = 10000;

#[derive(Debug, PartialEq, Eq, Hash)]
struct KeyValue {
    key: String,
    value: String,
}

/// Generates a hash table for use in tests containing 10,000 entries.
fn generate_hash_table() -> HashMap<String, String> {
    let mut hash_table = HashMap::new();

    for i in 0..NUM_TEST_VALUES {
        let key = format!("{}", i);
        let value = key.clone();
        hash_table.insert(key, value);
    }

    hash_table
}

/// Basic allocate and free test.
#[test]
fn test_hash_table_new_free() {
    let mut hash_table = HashMap::new();

    // Add some values
    hash_table.insert(value1, value1);
    hash_table.insert(value2, value2);
    hash_table.insert(value3, value3);
    hash_table.insert(value4, value4);

    // Test out of memory scenario
    // Note: Rust's memory allocation failure is handled by panic, not return NULL
    // This test is more about ensuring the hash table can be created and freed
    assert!(hash_table.len() == 4);
}

/// Test insert and lookup functions.
#[test]
fn test_hash_table_insert_lookup() {
    let hash_table = generate_hash_table();

    assert_eq!(hash_table.len(), NUM_TEST_VALUES);

    // Check all values
    for i in 0..NUM_TEST_VALUES {
        let key = format!("{}", i);
        assert_eq!(hash_table.get(&key), Some(&key));
    }

    // Lookup on invalid values returns None
    assert_eq!(hash_table.get("-1"), None);
    assert_eq!(hash_table.get(&format!("{}", NUM_TEST_VALUES)), None);

    // Insert overwrites existing entries with the same key
    let key = "12345".to_string();
    hash_table.insert(key.clone(), "hello world".to_string());
    assert_eq!(hash_table.get(&key), Some(&"hello world".to_string()));
}

/// Test remove function.
#[test]
fn test_hash_table_remove() {
    let mut hash_table = generate_hash_table();

    assert_eq!(hash_table.len(), NUM_TEST_VALUES);
    let key = "5000".to_string();
    assert_eq!(hash_table.get(&key), Some(&key));

    // Remove an entry
    hash_table.remove(&key);

    // Check entry counter
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);

    // Check that None is returned now
    assert_eq!(hash_table.get(&key), None);

    // Try removing a non-existent entry
    hash_table.remove("-1");
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);
}

/// Test iterating over the hash table.
#[test]
fn test_hash_table_iterating() {
    let hash_table = generate_hash_table();

    // Iterate over all values in the table
    let count = hash_table.iter().count();
    assert_eq!(count, NUM_TEST_VALUES);

    // Test iterating over an empty table
    let empty_hash_table: HashMap<i32, i32> = HashMap::new();
    let count = empty_hash_table.iter().count();
    assert_eq!(count, 0);
}

/// Demonstrates the ability to iteratively remove objects from the hash table.
#[test]
fn test_hash_table_iterating_remove() {
    let mut hash_table = generate_hash_table();

    // Iterate over all values in the table
    let mut count = 0;
    let mut removed = 0;

    let keys: Vec<String> = hash_table.keys().cloned().collect();
    for key in keys {
        if key.parse::<i32>().unwrap() % 100 == 0 {
            hash_table.remove(&key);
            removed += 1;
        }
        count += 1;
    }

    // Check counts
    assert_eq!(removed, 100);
    assert_eq!(count, NUM_TEST_VALUES);
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - removed);

    // Check all entries divisible by 100 were really removed
    for i in 0..NUM_TEST_VALUES {
        let key = format!("{}", i);
        if i % 100 == 0 {
            assert_eq!(hash_table.get(&key), None);
        } else {
            assert_ne!(hash_table.get(&key), None);
        }
    }
}

/// Test the use of functions to free keys / values when they are removed.
#[test]
fn test_hash_table_free_functions() {
    let mut hash_table = HashMap::new();

    // Create a hash table, fill it with values
    for i in 0..NUM_TEST_VALUES {
        let key = Box::new(i);
        let value = Box::new(99);
        hash_table.insert(key, value);
    }

    assert_eq!(hash_table.len(), NUM_TEST_VALUES);

    // Check that removing a key works
    let key = NUM_TEST_VALUES / 2;
    hash_table.remove(&key);
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);

    // Check that replacing an existing key works
    let key = NUM_TEST_VALUES / 3;
    let value = Box::new(999);
    hash_table.insert(Box::new(key), value);
    assert_eq!(hash_table.len(), NUM_TEST_VALUES - 1);

    // A free of the hash table should free all of the keys and values
    // Note: Rust automatically handles memory deallocation
}

/// Test for out of memory scenario.
#[test]
fn test_hash_table_out_of_memory() {
    let mut hash_table = HashMap::new();

    // Test normal failure
    // Note: Rust's memory allocation failure is handled by panic, not return NULL
    // This test is more about ensuring the hash table can be created and freed
    assert!(hash_table.len() == 0);

    // Test failure when increasing table size
    for i in 0..65 {
        hash_table.insert(i, i);
    }
    assert_eq!(hash_table.len(), 65);

    // Test the 66th insert
    // Note: Rust's memory allocation failure is handled by panic, not return NULL
    // This test is more about ensuring the hash table can be created and freed
    assert!(hash_table.len() == 65);
}

/// Test iterator key-value pair retrieval.
#[test]
fn test_hash_iterator_key_pair() {
    let mut hash_table = HashMap::new();

    // Add some values
    hash_table.insert(value1, value1);
    hash_table.insert(value2, value2);

    for (key, value) in hash_table.iter() {
        assert_eq!(key, value);
    }
}

fn main() {
    // Run tests
    let test_result = run_tests();
    std::process::exit(test_result);
}

fn run_tests() -> i32 {
    // Run all tests
    test_hash_table_new_free();
    test_hash_table_insert_lookup();
    test_hash_table_remove();
    test_hash_table_iterating();
    test_hash_table_iterating_remove();
    test_hash_table_free_functions();
    test_hash_table_out_of_memory();
    test_hash_iterator_key_pair();

    0
}
